﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Evaluant.Uss.ObjectContext;
using Evaluant.OPath.Expressions;
using Evaluant.OPath.Expressions.Collections;
using System.Linq.Expressions;

namespace Evaluant.Uss.Linq
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TQuery">The type over which the query is executed (return type)</typeparam>
    /// <typeparam name="TResult">The result of the Select clause</typeparam>
    public class LinqQuery<TQuery, TResult> : IEnumerable<TResult>, IInferrable<TResult>
        where TQuery : class
        where TResult : class
    {
        QueryTranslator translator;
        public Evaluant.Uss.ObjectContext.ObjectContext ObjectContext { get; set; }

        internal LinqQuery(QueryTranslator translator, Query query, Evaluant.Uss.ObjectContext.ObjectContext oc)
        {
            this.translator = translator;
            this.Query = query;
            this.ObjectContext = oc;
        }

        public LinqQuery(Type root, Evaluant.Uss.ObjectContext.ObjectContext oc)
        {
            this.Query = new Query(root);

            Identifier i = new Identifier(String.Empty, null);
            translator = new QueryTranslator(oc);
            translator.Path.Identifiers.Add(i);
            translator.Identifiers.Add(String.Empty, i);

            this.ObjectContext = oc;
        }

        public LinqQuery<TQuery, TResult> Infer(System.Linq.Expressions.Expression<Func<TQuery, object>> selector)
        {
            Query.InferredRelationships.Add(((System.Linq.Expressions.MemberExpression)(selector.Body)).Member.Name);
            return this;
        }

        public Query Query { get; set; }

        #region IEnumerable<TResult> Members

        public IEnumerator<TResult> GetEnumerator()
        {
            if (InferredRelationships != null)
            {
                foreach (string inferred in InferredRelationships)
                {
                    Query.InferredRelationships.Add(inferred);
                }
            }

            IEnumerable<TResult> result = null;

            if ((Selector == null && typeof(TResult) == typeof(TQuery)) || (typeof(TQuery).IsGenericType && typeof(TQuery).FullName.Contains("AnonymousType")))
            {
                result = ObjectContext.Load<TResult>(Query);
            }
            else
            {
                if (typeof(TResult) != typeof(TQuery) && typeof(TResult) != typeof(string) && !typeof(TResult).IsPrimitive)
                    result = ObjectContext.Load<TResult>(Query);

                if (result == null)
                {
                    Func<TQuery, TResult> selector = Selector.Compile();
                    result = ObjectContext.Load<TQuery>(Query).Select<TQuery, TResult>(selector);
                }
            }

            if (DetachResult && ObjectContext.PersistenceEngine.Model.GetEntity(Evaluant.Uss.ObjectContext.ObjectContext.ConvertNamespaceDomainToEuss(typeof(TResult))) != null)
            {
                List<TResult> detached = new List<TResult>(result);
                result = detached.ConvertAll(delegate(TResult r) { return ObjectContext.Detach<TResult>(r); });
            }

            return result.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        internal QueryTranslator Translator
        {
            get { return translator; }
            set { translator = value; }
        }

        public Expression<Func<TQuery, TResult>> Selector { get; set; }


        public List<string> InferredRelationships
        {
            get;
            set;
        }

        #region IInferrable<TResult> Members

        public bool DetachResult { get; set; }


        public IInferrable Infer(string relationship)
        {
            InferredRelationships.Add(relationship);
            return this;
        }

        public IInferrable Infer<T>(System.Linq.Expressions.Expression<Func<T, object>> relationship)
        {
            return Infer<T>(relationship);
        }

        public IInferrable<TResult> Infer(System.Linq.Expressions.Expression<Func<TResult, object>> relationship)
        {
            Infer(((System.Linq.Expressions.MemberExpression)(relationship.Body)).Member.Name);
            return this;
        }

        #endregion
    }
}
